<!DOCTYPE html>
<html lang="en">

<head>
    <title>cannon.js cloth simulation</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
    <style>
        body {
            font-family: Monospace;
            background-color: #000;
            color: #000;
            margin: 0px;
            overflow: hidden;
        }

        #info {
            text-align: center;
            padding: 10px;
            z-index: 10;
            width: 100%;
            position: absolute;
        }

        a {
            text-decoration: underline;
            cursor: pointer;
        }
    </style>
</head>

<body>
    <script src="../libs/Three.js"></script>
    <script src="../libs/TrackballControls.js"></script>
    <script src="../libs/Detector.js"></script>
    <script src="../../feng3d/out/feng3d.js"></script>
    <script src="../../physics/out/physics.js"></script>
    <script>

        var dt = 1 / 60, R = 0.2;

        var clothMass = 1;  // 1 kg in total
        var clothSize = 1; // 1 meter
        var Nx = 12;
        var Ny = 12;
        var mass = clothMass / Nx * Ny;

        var restDistance = clothSize / Nx;

        var ballSize = 0.1;

        var clothFunction = plane(restDistance * Nx, restDistance * Ny);

        function plane(width, height)
        {
            return function (u, v)
            {
                var x = (u - 0.5) * width;
                var y = (v + 0.5) * height;
                var z = 0;
                return new THREE.Vector3(x, y, z);
            };
        }

        if (!Detector.webgl) Detector.addGetWebGLMessage();

        var container, stats;
        var camera, scene, renderer;

        var clothGeometry;
        var sphereMesh, sphereBody;
        var object;
        var particles = [];
        var world;

        initCannon();
        init();
        animate();

        function initCannon()
        {
            world = new physics.World();
            world.broadphase = new physics.NaiveBroadphase();
            world.gravity.set(0, -9.82, 0);
            world.solver.iterations = 20;

            // Materials
            var clothMaterial = new physics.Material();
            var sphereMaterial = new physics.Material();
            var clothSphereContactMaterial = new physics.ContactMaterial(clothMaterial,
                sphereMaterial,
                0.0, // friction coefficient
                0.0  // restitution
            );
            // Adjust constraint equation parameters for ground/ground contact
            clothSphereContactMaterial.contactEquationStiffness = 1e9;
            clothSphereContactMaterial.contactEquationRelaxation = 3;

            // Add contact material to the world
            world.addContactMaterial(clothSphereContactMaterial);

            // Create sphere
            var sphereShape = new physics.Sphere(ballSize * 1.3);
            sphereBody = new physics.Body({
                mass: 0
            });
            sphereBody.addShape(sphereShape);
            sphereBody.position.set(0, 0, 0);
            world.addBody(sphereBody);

            // Create cannon particles
            for (var i = 0, il = Nx + 1; i !== il; i++)
            {
                particles.push([]);
                for (var j = 0, jl = Ny + 1; j !== jl; j++)
                {
                    var idx = j * (Nx + 1) + i;
                    var p = clothFunction(i / (Nx + 1), j / (Ny + 1));
                    var particle = new physics.Body({
                        mass: j == Ny ? 0 : mass
                    });
                    particle.addShape(new physics.Particle());
                    particle.linearDamping = 0.5;
                    particle.position.set(
                        p.x,
                        p.y - Ny * 0.9 * restDistance,
                        p.z
                    );
                    particles[i].push(particle);
                    world.addBody(particle);
                    particle.velocity.set(0, 0, -0.1 * (Ny - j));
                }
            }
            function connect(i1, j1, i2, j2)
            {
                world.addConstraint(new physics.DistanceConstraint(particles[i1][j1], particles[i2][j2], restDistance));
            }
            for (var i = 0; i < Nx + 1; i++)
            {
                for (var j = 0; j < Ny + 1; j++)
                {
                    if (i < Nx) connect(i, j, i + 1, j);
                    if (j < Ny) connect(i, j, i, j + 1);
                }
            }
        }

        function init()
        {

            container = document.createElement('div');
            document.body.appendChild(container);

            // scene

            scene = new THREE.Scene();

            scene.fog = new THREE.Fog(0x000000, 500, 10000);

            // camera

            camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 0.5, 10000);

            camera.position.set(Math.cos(Math.PI / 4) * 3,
                0,
                Math.sin(Math.PI / 4) * 3);

            scene.add(camera);


            // Controls
            controls = new THREE.TrackballControls(camera);

            controls.rotateSpeed = 1.0;
            controls.zoomSpeed = 1.2;
            controls.panSpeed = 0.8;

            controls.noZoom = false;
            controls.noPan = false;

            controls.staticMoving = true;
            controls.dynamicDampingFactor = 0.3;

            controls.keys = [65, 83, 68];


            // lights
            var light, materials;
            scene.add(new THREE.AmbientLight(0x666666));

            light = new THREE.DirectionalLight(0xffffff, 1.75);
            var d = 5;

            light.position.set(d, d, d);

            light.castShadow = true;
            //light.shadowCameraVisible = true;

            light.shadowMapWidth = 1024 * 2;
            light.shadowMapHeight = 1024 * 2;

            light.shadowCameraLeft = -d;
            light.shadowCameraRight = d;
            light.shadowCameraTop = d;
            light.shadowCameraBottom = -d;

            light.shadowCameraFar = 3 * d;
            light.shadowCameraNear = d;
            light.shadowDarkness = 0.5;

            scene.add(light);

            /*
            light = new THREE.DirectionalLight( 0xffffff, 0.35 );
            light.position.set( 0, -1, 0 );

            scene.add( light );
            */

            // cloth material

            var clothTexture = THREE.ImageUtils.loadTexture('sunflower.jpg'); // circuit_pattern.png
            clothTexture.wrapS = clothTexture.wrapT = THREE.RepeatWrapping;
            clothTexture.anisotropy = 16;


            var clothMaterial = new THREE.MeshPhongMaterial({
                alphaTest: 0.5,
                ambient: 0x000000,
                color: 0xffffff,
                specular: 0x333333,
                emissive: 0x222222,
                //shininess: 5,
                map: clothTexture,
                side: THREE.DoubleSide
            });

            // cloth geometry
            clothGeometry = new THREE.ParametricGeometry(clothFunction, Nx, Ny, true);
            clothGeometry.dynamic = true;
            clothGeometry.computeFaceNormals();

            // cloth mesh
            object = new THREE.Mesh(clothGeometry, clothMaterial);
            object.position.set(0, 0, 0);
            object.castShadow = true;
            //object.receiveShadow = true;
            scene.add(object);

            // sphere
            var ballGeo = new THREE.SphereGeometry(ballSize, 20, 20);
            var ballMaterial = new THREE.MeshPhongMaterial({ color: 0x888888 });

            sphereMesh = new THREE.Mesh(ballGeo, ballMaterial);
            sphereMesh.castShadow = true;
            //sphereMesh.receiveShadow = true;
            scene.add(sphereMesh);


            renderer = new THREE.WebGLRenderer({ antialias: true });
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.setClearColor(scene.fog.color);

            container.appendChild(renderer.domElement);

            renderer.gammaInput = true;
            renderer.gammaOutput = true;
            renderer.physicallyBasedShading = true;

            renderer.shadowMapEnabled = true;

            window.addEventListener('resize', onWindowResize, false);

            camera.lookAt(sphereMesh.position);
        }

        //

        function onWindowResize()
        {

            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            controls.handleResize();

            renderer.setSize(window.innerWidth, window.innerHeight);

        }

        function animate()
        {
            requestAnimationFrame(animate);
            controls.update();
            world.step(dt);
            var t = world.time;
            sphereBody.position.set(R * Math.sin(t), 0, R * Math.cos(t));
            render();
        }

        function render()
        {

            for (var i = 0, il = Nx + 1; i !== il; i++)
            {
                for (var j = 0, jl = Ny + 1; j !== jl; j++)
                {
                    var idx = j * (Nx + 1) + i;
                    clothGeometry.vertices[idx].copy(particles[i][j].position);
                }
            }

            clothGeometry.computeFaceNormals();
            clothGeometry.computeVertexNormals();

            clothGeometry.normalsNeedUpdate = true;
            clothGeometry.verticesNeedUpdate = true;

            sphereMesh.position.copy(sphereBody.position);

            renderer.render(scene, camera);

        }

    </script>
</body>

</html>